#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory.Core.Common;
using BitwiseOre.SharedMemory.Core.IPCQueues;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.IPCQueues
{
    [TestFixture]
    public sealed class ConnectionQueuesFixture : AssertionHelper
    {
        [Test(Description = "Shutting down writes on the connection queues flushes the write buffers twice.")]
        public void ShutdownWrite_FlushesWriteBuffers()
        {
            var writebuffers = new Mock<IGuardedEnumerator<IWriteBuffer>>();
            writebuffers.Setup(wb => wb.MoveNext(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => true));

            var conn = new ConnectionQueues(writebuffers.Object,
                                            new Mock<IGuardedEnumerator<IReadBuffer>>().Object);
            conn.ShutdownWrite(TimeSpan.FromMinutes(1)).Wait();

            writebuffers.Verify(w => w.Flush(), Times.Exactly(2), "Write buffers were not flushed twice.");
        }

        [Test(Description = "Shutting down writes on the connection queues moves to the next write buffer.")]
        public void ShutdownWrite_MovesToNextBuffer()
        {
            var writebuffers = new Mock<IGuardedEnumerator<IWriteBuffer>>();
            writebuffers.Setup(wb => wb.MoveNext(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => true));

            var conn = new ConnectionQueues(writebuffers.Object,
                                            new Mock<IGuardedEnumerator<IReadBuffer>>().Object);
            conn.ShutdownWrite(TimeSpan.FromMinutes(1)).Wait();
            
            writebuffers.Verify(wb => wb.MoveNext(It.IsAny<TimeSpan>()),
                                "Connection queues did not move to the next write buffer.");
        }

        [Test(Description = "Shutting down writes on the connection queue ends the enumeration of the " +
                            "write buffers.")]
        public void ShutdownWrite_EndsWriteEnumeration()
        {
            var writebuffers = new Mock<IGuardedEnumerator<IWriteBuffer>>();
            writebuffers.Setup(wb => wb.MoveNext(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => true));

            var conn = new ConnectionQueues(writebuffers.Object,
                                            new Mock<IGuardedEnumerator<IReadBuffer>>().Object);
            conn.ShutdownWrite(TimeSpan.FromMinutes(1)).Wait();
            
            writebuffers.Verify(wb => wb.EndEnumeration(), "The write buffer enumerations wad not ended.");
        }

        [Test(Description = "Reading from the current buffer delegates to the current read buffer.")]
        public void ReadFromCurrentBuffer_DelegatesToCurrentReadBuffer()
        {
            var array = new ArraySegment<byte>(new byte[5]);
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.ReadFromCurrentBuffer(array);

            readbuffers.Verify(r => r.Current.ReadArraySegment(It.Is<ArraySegment<byte>>(a => a == array)),
                               "No delegated call to the current read buffer.");
        }

        [Test(Description = "Reading from the current buffer returns the number of bytes read by " +
                            "the delegated read to the current read buffer.")]
        public void ReadFromCurrentBuffer_ReturnsDelegatedReadCount()
        {
            const int readcount = 5;
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.Current.ReadArraySegment(It.IsAny<ArraySegment<byte>>())).Returns(readcount);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            var result = conn.ReadFromCurrentBuffer(new ArraySegment<byte>(new byte[5]));
            
            Expect(result, EqualTo(readcount),
                   "Unexpected count returned from when reading from the current buffer.");
        }

        [Test(Description = "Reading from the current buffer returns 0 if there is no current buffer.")]
        public void ReadFromCurrentBuffer_ReturnsZeroIfNoCurrentBuffer()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.SetupGet(r => r.Current).Returns((IReadBuffer)null);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            var result = conn.ReadFromCurrentBuffer(new ArraySegment<byte>(new byte[5]));
            
            Expect(result, EqualTo(0),
                   "Unexpected count returned from when reading from the current buffer.");
            
        }

        [Test(Description = "Reading a byte from the current buffer returns -1 if there is not current buffer.")]
        public void ReadByteFromCurrentBuffer_ReturnsMinusOneIfNoCurrentBuffer()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.SetupGet(r => r.Current).Returns((IReadBuffer)null);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            var result = conn.ReadByteFromCurrentBuffer();

            Expect(result, EqualTo(-1), "Unexpected result returned when reading a byte from the current buffer.");
        }

        [Test(Description = "Reading from the next buffer moves to the next buffer.")]
        public void ReadFromNextBuffer_MovesToNextReadBuffer()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.ReadFromNextBuffer(new ArraySegment<byte>(new byte[5]), new Mock<ITimeoutHelper>().Object);

            readbuffers.Verify(r => r.MoveNext(It.IsAny<TimeSpan>()),"Did not move to the next read buffer.");
        }

        [Test(Description = "Reading from the next buffer ends with a fault state if moving to the next buffer " +
                            "is unsucessful.")]
        public void ReadFromNextBuffer_FaultsIfMoveNotSucessful()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => false));

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            var task = conn.ReadFromNextBuffer(new ArraySegment<byte>(new byte[5]),
                                               new Mock<ITimeoutHelper>().Object);
            try
            {
                task.Wait();
            }
            catch (AggregateException){}

            Expect(task.Status, EqualTo(TaskStatus.Faulted), "Returned task did not fault.");
        }

        [Test(Description = "Reading from the next buffer flushes the write buffer if the next read buffer " +
                            "is empty as a part of the disconnect protocol.")]
        public void ReadFromNextBuffer_FlushesWriteBuffersIfNextReadBufferIsEmpty()
        {
            var writebuffers = new Mock<IGuardedEnumerator<IWriteBuffer>>();
            writebuffers.Setup(w => w.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));
            readbuffers.SetupGet(r => r.Current.IsEmpty).Returns(true);

            var conn = new ConnectionQueues(writebuffers.Object, readbuffers.Object);
            conn.ReadFromNextBuffer(new ArraySegment<byte>(new byte[5]), new Mock<ITimeoutHelper>().Object).Wait();
            
            writebuffers.Verify(w => w.Flush(), "Failure to flush the write buffers.");
        }

        [Test(Description = "Reading from the next buffer delegates to the next read buffer if that buffer " +
                            "is not empty.")]
        public void ReadFromNextBuffer_DelegatesToNextReadBufferIfNotEmpty()
        {
            var array = new ArraySegment<byte>(new byte[5]);
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));
            readbuffers.SetupGet(r => r.Current.IsEmpty).Returns(false);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.ReadFromNextBuffer(array, new Mock<ITimeoutHelper>().Object).Wait();

            readbuffers.Verify(r => r.Current.ReadArraySegment(It.Is<ArraySegment<byte>>(a => a == array)),
                               "No delegated call to the next read buffer.");

        }

        [Test(Description = "Reading from the next buffer clears the current read buffer.")]
        public void ReadFromNextBuffer_ClearsCurrentReadBuffer()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));
            
            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.ReadFromNextBuffer(new ArraySegment<byte>(new byte[5]), new Mock<ITimeoutHelper>().Object).Wait();

            readbuffers.Verify(rb => rb.Current.Clear(),
                               "Reading from the next buffer did not clear the current one.");
        }


        [Test(Description = "Flushing the read buffer delgates to the read buffers if the current read " +
                            "buffer is past the end.")]
        public void FlushReadBuffer_DelegatesToReadBuffersIfCurrentIsPastEnd()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.SetupGet(rb => rb.Current.IsPastEnd).Returns(true);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.FlushReadBuffer();

           readbuffers.Verify(rb => rb.Flush(), "Flush not called on the read buffers.");
        }

        [Test(Description = "Flushing the read buffer does not delegate to the read buffers if the current " +
                            "read buffer is not past the end.")]
        public void FlushReadBuffer_DoesNotDelegateToReadBuffersIfCurrrentIsNotPastEnd()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.SetupGet(rb => rb.Current.IsPastEnd).Returns(false);

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.FlushReadBuffer();

           readbuffers.Verify(rb => rb.Flush(), Times.Never(),"Flush was called on the read buffers.");
            
        }

        [Test(Description = "Purging the read buffers moves to the next buffer if there is not current one.")]
        public void PurgeReadBuffers_MovesToNextReadBufferIfNoCurrentBuffer()
        {
            var readbuffers = new Mock<IGuardedEnumerator<IReadBuffer>>{DefaultValue = DefaultValue.Mock};
            readbuffers.Setup(r => r.MoveNext(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => true));
            var results = new Queue<IReadBuffer>(new[] {null, new Mock<IReadBuffer>().Object});
            readbuffers.SetupGet(r => r.Current).Returns(results.Dequeue);
            var helper = new Mock<ITimeoutHelper>();
            helper.SetupGet(h => h.RemainingTime).Returns(TimeSpan.FromMinutes(1));

            var conn = new ConnectionQueues(new Mock<IGuardedEnumerator<IWriteBuffer>>().Object, readbuffers.Object);
            conn.PurgeReadBuffers(helper.Object);

            readbuffers.Verify(r => r.MoveNext(It.IsAny<TimeSpan>()),
                               "Purging the read buffers did not move to the next read buffer.");
        }

    }

}